package com.sukaiyi.xxljob.logfinder;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
public class HttpBasedLogFinder implements LogFinder {

    private final RestTemplate restTemplate;

    public HttpBasedLogFinder(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    public void find(Param param, Predicate<Log> filter, Consumer<Log> consumer) {

        String pageUrl = param.getBaseUrl() + "/joblog/pageList";
        List<String> cookies = login(param);
        HttpHeaders headers = new HttpHeaders();
        headers.put(HttpHeaders.COOKIE, cookies);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("jobGroup", Optional.of(param).map(Param::getJobGroup).map(String::valueOf).orElse("0"));
        map.add("jobId", Optional.of(param).map(Param::getJobId).map(String::valueOf).orElse("0"));
        map.add("logStatus", Optional.of(param).map(Param::getLogStatus).map(String::valueOf).orElse("-1"));

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime start = Optional.of(param)
                .map(Param::getStartFilterTime)
                .orElseGet(() -> LocalDateTime.of(LocalDate.now(), LocalTime.MIN));
        LocalDateTime end = Optional.of(param)
                .map(Param::getEndFilterTime)
                .orElseGet(() -> LocalDateTime.of(LocalDate.now(), LocalTime.MAX));
        String filterTime = Stream.of(start, end)
                .map(e -> e.format(formatter))
                .collect(Collectors.joining(" - "));
        map.add("filterTime", filterTime);

        int fetched = 0; // 已经获取的条数
        int recordsTotal = 1; // 总条数
        while (fetched < recordsTotal) {
            map.put("start", Collections.singletonList(String.valueOf(fetched)));
            map.put("length", Collections.singletonList("10"));

            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
            ResponseEntity<LogResponse> response = restTemplate.postForEntity(pageUrl, request, LogResponse.class);
            if (response.getStatusCode() != HttpStatus.OK) {
                log.error("http request {} failed with status code: {}", pageUrl, response.getStatusCodeValue());
                break;
            }
            LogResponse logResponse = response.getBody();
            if (logResponse == null) {
                log.error("http request {} failed!", pageUrl);
                break;
            }
            List<Log> logs = logResponse.getData();
            fetched += logs.size();
            recordsTotal = logResponse.getRecordsTotal();

            filter = Optional.ofNullable(filter).orElse(e -> true);
            consumer = Optional.ofNullable(consumer).orElse(e -> {
            });

            logs.stream()
                    .peek(log -> log.setDetailUrl(param.getBaseUrl() + "/joblog/logDetailPage?id=" + log.getId()))
                    .peek(log -> fillDetail(param, log, cookies))
                    .filter(filter)
                    .forEach(consumer);
        }
    }

    @SuppressWarnings("rawtypes")
    private void fillDetail(Param param, Log log, List<String> cookies) {
        String detailUrl = param.getBaseUrl() + "/joblog/logDetailCat";
        HttpHeaders headers = new HttpHeaders();
        headers.put(HttpHeaders.COOKIE, cookies);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("executorAddress", log.getExecutorAddress());
        map.add("triggerTime", String.valueOf(log.getTriggerTime()));
        map.add("logId", String.valueOf(log.getId()));
        map.add("fromLineNum", "1");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
        ResponseEntity<Map> response = restTemplate.postForEntity(detailUrl, request, Map.class);

        if (response.getStatusCode() != HttpStatus.OK) {
            HttpBasedLogFinder.log.error("http request {} failed with status code: {}", detailUrl, response.getStatusCodeValue());
        }
        log.setDetail("");
        Map responseMap = response.getBody();
        Optional.ofNullable(responseMap)
                .map(e -> e.get("content"))
                .filter(e -> e instanceof Map)
                .map(e -> (Map) e)
                .map(e -> e.get("logContent"))
                .filter(e -> e instanceof String)
                .map(e -> (String) e)
                .ifPresent(log::setDetail);
        if (responseMap == null) {
            HttpBasedLogFinder.log.error("http request {} failed!", detailUrl);
        }
    }


    private List<String> login(Param param) {
        String loginUrl = param.getBaseUrl() + "/login";
        HttpHeaders headers = new HttpHeaders();
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("userName", param.getUsername());
        map.add("password", param.getPassword());
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
        ResponseEntity<String> response = restTemplate.postForEntity(loginUrl, request, String.class);

        return Optional.of(response)
                .map(HttpEntity::getHeaders)
                .map(e -> e.get("Set-Cookie"))
                .orElse(Collections.emptyList());
    }


    @Data
    public static final class LogResponse {
        private List<Log> data;
        private Integer recordsFiltered;
        private Integer recordsTotal;
    }
}
